﻿using System;
using System.Threading;
using System.Collections.Generic;
using VisualEsse.DataEngine.Types;
using VisualEsse;
using System.Windows;
using SW = System.Windows;
using System.Windows.Media.Imaging;
using System.Drawing.Imaging;
using System.Windows.Controls;

namespace MultiViewer
{
    public abstract class TiledImage
    {
        /// <summary>Entire image coordinates</summary>
        protected Rect entireImage;
        /// <summary>Minimal level of detail</summary>
        protected int minLevel;
        /// <summary>Maximal level of detail</summary>
        protected int maxLevel;
        /// <summary>Individual tile size (in pixels)</summary>
        protected Size tileSize;
        /// <summary>Screen proportion (width/height) of image part with equal 
        /// width and height in image coordinates or screen proportions of virtual pixel</remarks>
        protected double aspectRatio;
        /// <summary>Transparency of this image. Value of 1.0 means opaque image</summary>
        protected double alpha = 1.0;

        protected double dpiX;
        protected double dpiY;

        public TiledImage()
        {
            using (System.Drawing.Graphics g = System.Drawing.Graphics.FromHwnd(IntPtr.Zero))
            {
                dpiX = g.DpiX;
                dpiY = g.DpiY;
            }
        }

        public Rect EntireImage
        {
            get { return entireImage; }
        }

        public int MinLevel
        {
            get { return minLevel; }
        }

        public int MaxLevel
        {
            get { return maxLevel; }
        }

        public Size TileSize
        {
            get { return tileSize; }
        }

        public double AspectRatio
        {
            get { return aspectRatio; }
        }

        public double Alpha
        {
            get { return alpha; }
            set { alpha = value; }
        }

        public int GetZoom(SW.Rect output, Rect visible)
        {
            double entireWidth = entireImage.Width;
            double entireHeight = entireImage.Height;

            double tileWidth = tileSize.Width;
            double tileHeight = tileSize.Height;

            double visibleHeight = visible.Top - visible.Bottom;
            double visibleWidth = visible.Right - visible.Left;

            double pixelWidth = output.Width * dpiX / 96;
            double pixelHeight = output.Height * dpiY / 96;

            int zoom = (int)Math.Round(
                            (Math.Log(entireHeight * pixelHeight / (tileHeight * visibleHeight), 2) +
                             Math.Log(entireWidth * pixelWidth / (tileWidth * visibleWidth), 2)) / 2);
            if (zoom < minLevel)
                zoom = minLevel;
            else if (zoom > maxLevel)
                zoom = maxLevel;
            return zoom;
        }

        public abstract BitmapImage GetTile(int row, int col, int level);

        private delegate BitmapImage AsyncLoader(int row, int col, int level);

        public void Render(Canvas target, SW.Rect output, Rect visible)
        {
            double entireWidth = entireImage.Width;
            double entireHeight = entireImage.Height;

            double tileWidth = tileSize.Width;
            double tileHeight = tileSize.Height;

            double visibleHeight = visible.Top - visible.Bottom;
            double visibleWidth = visible.Right - visible.Left;

            double width = output.Width;
            double height = output.Height;

            int zoom = (int)Math.Round(
                (Math.Log(entireHeight * height / (tileHeight * visibleHeight), 2) +
                 Math.Log(entireWidth * width / (tileWidth * visibleWidth), 2)) / 2);
            if (zoom < minLevel)
                zoom = minLevel;
            else if (zoom > maxLevel)
                zoom = maxLevel;

            int zoomSq = 1 << zoom;

            int colMin = (int)Math.Floor((1 << zoom) * (visible.Left - entireImage.Left) / entireWidth);
            int colMax = (int)Math.Floor((1 << zoom) * (visible.Right - entireImage.Left) / entireWidth);

            if (colMax >= zoomSq)
                colMax = zoomSq - 1;

            int rowMin = (int)Math.Floor((1 << zoom) * (visible.Bottom - entireImage.Bottom) / entireHeight);
            int rowMax = (int)Math.Floor((1 << zoom) * (visible.Top - entireImage.Bottom) / entireHeight);

            if (rowMax >= zoomSq)
                rowMax = zoomSq - 1;

            AsyncLoader[,] loaders = new AsyncLoader[rowMax - rowMin + 1, colMax - colMin + 1];
            IAsyncResult[,] results = new IAsyncResult[rowMax - rowMin + 1, colMax - colMin + 1];

            BitmapImage[,] images = new BitmapImage[rowMax - rowMin + 1, colMax - colMin + 1];

            for (int row = rowMin; row <= rowMax; row++)
                for (int col = colMin; col <= colMax; col++)
                {
                    int i = row - rowMin;
                    int j = col - colMin;
                    images[i, j] = GetTile(row, col, zoom);
                    /*loaders[i, j] = new AsyncLoader(
                        delegate(int y, int x, int z)
                        {
                            return GetTile(y, x, z);
                        });
                    results[i, j] = loaders[i, j].BeginInvoke(row, col, zoom, null, null);*/
                }

            //foreach (IAsyncResult ar in results)
            //    ar.AsyncWaitHandle.WaitOne();

            for (int row = rowMin; row <= rowMax; row++)
                for (int col = colMin; col <= colMax; col++)
                {
                    Rect tileRect = new Rect();
                    tileRect.Left = entireWidth * col / (1 << zoom) + entireImage.Left;
                    tileRect.Right = entireWidth * (col + 1) / (1 << zoom) + entireImage.Left;
                    tileRect.Bottom = entireHeight * (row) / (1 << zoom) + entireImage.Bottom;
                    tileRect.Top = entireHeight * (row + 1) / (1 << zoom) + entireImage.Bottom;

                    double xmin = output.Left + (int)((tileRect.Left - visible.Left) * width / visibleWidth);
                    double xmax = output.Left + (int)((tileRect.Right - visible.Left) * width / visibleWidth);
                    double ymin = output.Top + (int)((visible.Top - tileRect.Top) * height / visibleHeight);
                    double ymax = output.Top + (int)((visible.Top - tileRect.Bottom) * height / visibleHeight);
                    SW.Rect imageRect = new SW.Rect(xmin, ymin, xmax - xmin, ymax - ymin);

                    BitmapImage tile = images[row - rowMin, col - colMin];
                    /*try
                    {
                        tile = loaders[row - rowMin, col - colMin].EndInvoke(results[row - rowMin, col - colMin]);
                    }
                    catch
                    {
                        tile = new BitmapImage(); // TODO: Set uri for failed to load image
                    }*/
                    Image img = new Image();
                    if (imageRect.Width > tile.Width || imageRect.Height > tile.Height)
                    {
                        CroppedBitmap cb = new CroppedBitmap(tile, new Int32Rect(0, 0, (int)tile.Width - 1, (int)tile.Height - 1));
                        img.Source = cb;
                    }
                    else
                        img.Source = tile;
                    img.Width = imageRect.Width;
                    img.Height = imageRect.Height;
                    img.Stretch = System.Windows.Media.Stretch.Fill;
                    target.Children.Add(img);
                    Canvas.SetLeft(img, imageRect.Left);                    
                    Canvas.SetTop(img, imageRect.Top);
            }
        }

        /// <summary>
        /// x, y are virtual coordinates (inside image)
        /// </summary>
        public virtual bool OnClick(double x, double y, int zoom, double epsilon, Point screenPoint)
        {
            return false;
        }

/*        public static TiledImage FromNameString(string name)
        {
            if (name == "@VirtualEarth")
                return new VirtualEarthImage();
            else
                return new FileTiledImage(name);
        }*/

        public abstract void Dispose();
    }

    public interface IBoundToEarthImage
    {
        GeoRect GetTileRect(int row, int col, int level);

        Projection Projection { get; }
    }

    public enum Projection
    {
        Linear, Mercator
    }

}

